package upm.pb4agent.team5;

import upm.pb4agent.ontology.PB4AgentOntology;
import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec;
import jade.content.lang.Codec.CodecException;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.wrapper.AgentController;
import jade.wrapper.ContainerController;
import jade.wrapper.StaleProxyException;

@SuppressWarnings("serial")
public abstract class BaseAgent extends Agent {
	private final static int WAIT_PER_SEARCH = 100; // Milliseconds

	private Codec codec = null;
    private Ontology ontology = null;
    private String dfName = null; // Agent name to be used in DF    

    protected BaseAgent(String dfName) {
		setCodec(new SLCodec());
		setOntology(PB4AgentOntology.getInstance());
		setDfName(dfName);
		
		// Register of the codec and the ontology to be used in the ContentManager
        getContentManager().registerLanguage(codec);
        getContentManager().registerOntology(ontology);		
	}

    /**
     * Register specified agent in DF, using its name
     * @param baseAgent Agent to be registered in DF
     * @param name Name of the agent
     * @throws FIPAException
     */
	protected void registerAgent() throws FIPAException {
		DFAgentDescription dfd = new DFAgentDescription();
		ServiceDescription sd = new ServiceDescription();
		sd.setName(this.getName());
		sd.setType(this.getDfName());
		dfd.addServices(sd);
		
		// Registers its description in the DF
		DFService.register(this, dfd);
	}

	/**
	 * Send message using codec and ontology predefined
	 * @param concept
	 * @param receiver
	 * @param typeMsg
	 * @throws OntologyException 
	 * @throws CodecException 
	 */
	public void send(Concept concept, AID receiver, int typeMsg) throws CodecException, OntologyException {
		ACLMessage msg = new ACLMessage(typeMsg);
		msg.addReceiver(receiver);
		msg.setLanguage(codec.getName());
		msg.setOntology(ontology.getName());
		msg.setProtocol(concept.getClass().getSimpleName());
		
		Action action = new Action(receiver, concept);
		getContentManager().fillContent(msg, action);

		//System.out.println(msg);
		send(msg);
	}

	public void send(AID receiver, int typeMsg, String protocol) throws CodecException, OntologyException {
		ACLMessage msg = new ACLMessage(typeMsg);
		msg.addReceiver(receiver);
		msg.setLanguage(codec.getName());
		msg.setOntology(ontology.getName());
		msg.setProtocol(protocol);
		
		//System.out.println(msg);
		send(msg);
	}

	public void send(Object obj, AID receiver, int typeMsg, String protocol) throws CodecException, OntologyException {
		ACLMessage msg = new ACLMessage(typeMsg);
		msg.addReceiver(receiver);
		msg.setLanguage(codec.getName());
		msg.setOntology(ontology.getName());
		msg.setProtocol(protocol);
		msg.setContent(obj.toString());

		//System.out.println(msg);
		send(msg);
	}	
	
	/**
	 * Returns concept from ontology based on a specific ACLMessage
	 * @param msg ACLMessage
	 * @return Concept
	 * @throws UngroundedException
	 * @throws CodecException
	 * @throws OntologyException
	 */
	public Concept getConcept(ACLMessage msg) throws UngroundedException, CodecException, OntologyException {
		ContentElement ce = getContentManager().extractContent(msg);
		
		Action action = (Action) ce;
		Concept concept = action.getAction();
		
		return concept;
	}
	
	/**
	 * 
	 * @param dfName
	 * @return
	 * @throws FIPAException
	 */
	public DFAgentDescription[] search(String dfName) throws FIPAException {
		DFAgentDescription dfd = new DFAgentDescription();
		ServiceDescription sd = new ServiceDescription();
		sd.setType(dfName);
		dfd.addServices(sd);

		return DFService.search(this, dfd);		
	}
	
	/**
	 * Creates an agent with the specified dfName, if returnAID is true, it will return AID returned by DF
	 * @param agentClass Class file correspondent to Agent that is going to be created
	 * @param dfName DF_NAME
	 * @param returnAID Specify if an AID produced by DF.search is needed as return
	 * @return AID Can be null if returnAID is false, or AID registered in DF if it's true 
	 * @throws StaleProxyException When agent creation fails
	 * @throws InterruptedException When Thread.sleep fails, for waiting between DF.search
	 * @throws FIPAException When DF fails
	 */
	@SuppressWarnings("rawtypes")
	public AID createAgent(Class agentClass, String dfName, Object[] params, boolean returnAID) throws StaleProxyException, InterruptedException, FIPAException {
		ContainerController container = getContainerController();
		AID aid = null;
		
		if (params == null)
			params = new Object[] {};
		
		AgentController agent = container.createNewAgent(dfName, 
				agentClass.getCanonicalName(), 
				params
			);
		agent.start();
		
		// Adding agent to array (searching AID)
		if (returnAID) {
			while (aid == null) { // When found continue
				Thread.sleep(WAIT_PER_SEARCH); // Waiting WAIT_PER_SEARCH
				aid = findAID(dfName);
			}			
		}
		
		return aid;
	}
	
	/**
	 * Returns first ocurrence found for a specified dfName
	 * If nothing found returns null
	 * @param dfName DF_NAME
	 * @return AID Can be null
	 * @throws FIPAException
	 */
	public AID findAID(String dfName) throws FIPAException {
		DFAgentDescription[] dfd = search(dfName);
		AID aid = null;
		
		if (dfd!=null && dfd.length>0)
			aid = dfd[0].getName();
		
		return aid;
	}

	/**
	 * Getters and setters
	 */
	
	public Codec getCodec() {
		return codec;
	}

	protected void setCodec(Codec codec) {
		this.codec = codec;
	}

	public Ontology getOntology() {
		return ontology;
	}

	protected void setOntology(Ontology ontology) {
		this.ontology = ontology;
	}

	public String getDfName() {
		return dfName;
	}

	protected void setDfName(String dfName) {
		this.dfName = dfName;
	}
}
